home *** CD-ROM | disk | FTP | other *** search
/ Software Vault: The Gold Collection / Software Vault - The Gold Collection (American Databankers) (1993).ISO / cdr47 / tsrsrc34.zip / RELNET.PAS < prev    next >
Pascal/Delphi Source File  |  1993-04-17  |  44KB  |  1,374 lines

  1. {**************************************************************************
  2. *   RELNET - releases memory above the last MARKNET call made.            *
  3. *   Copyright (c) 1986,1991 Kim Kokkonen, TurboPower Software.            *
  4. *   May be freely distributed and used but not sold except by permission. *
  5. ***************************************************************************
  6. *   Version 2.7 3/4/89                                                    *
  7. *     first public release                                                *
  8. *     (based on RELEASE 2.6)                                              *
  9. *   Version 2.8 3/10/89                                                   *
  10. *     restore the DOS environment                                         *
  11. *     restore the async ports                                             *
  12. *   Version 2.9 5/4/89                                                    *
  13. *     ignore file marks                                                   *
  14. *   Version 3.0 9/25/91                                                   *
  15. *     make compatible with DOS 5                                          *
  16. *     handle NetWare IPX better, allowing release of NETBIOS TSR          *
  17. *     add Quiet option                                                    *
  18. *     update for new WATCH behavior                                       *
  19. *     restore BIOS LPT port data areas                                    *
  20. *     restore XMS allocation                                              *
  21. *     add code for tracking high memory                                   *
  22. *   Version 3.1 11/4/91                                                   *
  23. *     restore less of DOS variables table (more deactivates high memory   *
  24. *       after a release)                                                  *
  25. *     add option to disable IPX socket shutdown                           *
  26. *   Version 3.2 11/22/91                                                  *
  27. *     version 3.1 crashed under DOS 3.3 (RestoreDosTable)                 *
  28. *     change method of accessing high memory                              *
  29. *     reverse order in which memory blocks are released to work           *
  30. *       correctly with the 386MAX high memory manager                     *
  31. *     merge blocks in high memory after release (QEMM doesn't)            *
  32. *   Version 3.3 1/8/92                                                    *
  33. *     add /H to use high memory optionally                                *
  34. *     new features for parsing and getting command line options           *
  35. *   Version 3.4 2/14/92                                                   *
  36. *     release HMA when appropriate                                        *
  37. *     fix hang that occurs when QEMM LOADHI didn't have space to          *
  38. *       load a mark high                                                  *
  39. ***************************************************************************
  40. *   Telephone: 719-260-6641, CompuServe: 76004,2611.                      *
  41. *   Requires Turbo Pascal 6 to compile.                                   *
  42. ***************************************************************************}
  43.  
  44. {$R-,S-,I-,V-,B-,F-,A-,E-,N-,G-,X-}
  45. {$M 16384,0,655360}
  46. {.$DEFINE Debug}
  47.  
  48. program RelNet;
  49.  
  50. uses
  51.   Dos,
  52.   MemU,
  53.   Ipx,
  54.   Xms,
  55.   Ems;
  56.  
  57. const
  58.   MarkFOpen : Boolean = False;       {True while mark file is open}
  59.   VectorsRestored : Boolean = False; {True after old vector table restored}
  60.  
  61. var
  62.   Blocks : BlockArray;
  63.   markBlock : BlockType;
  64.   BlockMax : BlockType;
  65.   markPsp : Word;
  66.  
  67.   MarkName : PathStr;
  68.  
  69.   ReturnCode : Word;
  70.   StartMCB : Word;
  71.   HiMemSeg : Word;
  72.  
  73.   Revector8259 : Boolean;
  74.   DealWithIpx : Boolean;
  75.   DealWithEMS : Boolean;
  76.   DealWithXMS : Boolean;
  77.   KeepMark : Boolean;
  78.   RestoreEnvir : Boolean;
  79.   ResetTimer : Boolean;
  80.   RestoreComm : Boolean;
  81.   MemMark : Boolean;
  82.   FilMark : Boolean;
  83.   Verbose : Boolean;
  84.   Quiet : Boolean;
  85.   OptUseHiMem, UseHiMem : Boolean;
  86.  
  87.   Keys : string[16];
  88.  
  89.   MarkEHandles : Word;
  90.   CurrEHandles : Word;
  91.   MarkEmsHandles : PageArrayPtr;
  92.   CurrEmsHandles : PageArrayPtr;
  93.  
  94.   TrappedBytes : LongInt;
  95.  
  96.   MarkXHandles : Word;
  97.   CurrXHandles : Word;
  98.   MarkXmsHandles : XmsHandlesPtr;
  99.   CurrXmsHandles : XmsHandlesPtr;
  100.   MarkHmaStatus : Byte;
  101.   CurHmaStatus : Byte;
  102.  
  103.   {Save areas read in from file mark}
  104.   Vectors : array[0..1023] of Byte;
  105.   EGAsavTable : array[0..7] of Byte;
  106.   IntComTable : array[0..15] of Byte;
  107.   ParentSeg : Word;
  108.   ParentLen : Word;
  109.   BiosPrintTable : array[0..9] of Byte;
  110.   DevA : DeviceArray;             {Temporary array of device headers}
  111.   DevCnt : Word;                  {Number of device headers}
  112.   CommandPsp : array[1..$100] of Byte; {Buffer for COMMAND.COM PSP}
  113.   DosData : array[1..$200] of Byte; {Buffer for DOS data area}
  114.   DosTableSize : Word;
  115.   DosTable : Pointer;             {Dos internal variables}
  116.   FileTableA : array[1..5] of SftRecPtr; {Points to system file table buffers}
  117.   FileTableCnt : Word;            {Number of system file table blocks}
  118.   FileRecSize : Word;             {Bytes in internal DOS file record}
  119.   PatchOfst : Word;               {Address of COMMAND.COM patch}
  120.   PatchSegm : Word;
  121.   EnvLen : Word;                  {Bytes in DOS environment}
  122.   EnvPtr : Pointer;               {Pointer to copy of DOS environment}
  123.   PicMask : Byte;                 {8259 interrupt mask}
  124.   ComData : ComArray;             {Communications data array}
  125.   McbG : McbGroup;                {Allocated Mcbs}
  126.  
  127.   TestPtr : DeviceHeaderPtr;      {Test pointer while getting started on chain}
  128.   DevicePtr : DeviceHeaderPtr;    {Pointer to the next device header}
  129.   DeviceSegment : Word;           {Current device segment}
  130.   DeviceOffset : Word;            {Current device offset}
  131.   MarkF : file;                   {Saved system information file}
  132.   DosPtr : ^DosRec;               {Pointer to internal DOS variable table}
  133.   CommandSeg : Word;              {Segment of primary COMMAND.COM}
  134.  
  135.   procedure NoRestoreHalt(ReturnCode : Word);
  136.     {-Replace Turbo halt with one that doesn't restore any interrupts}
  137.   begin
  138.     if VectorsRestored then begin
  139.       Close(Output);
  140.       asm
  141.         mov ah,$4C
  142.         mov al,byte(ReturnCode)
  143.         int $21
  144.       end;
  145.     end else
  146.       System.Halt(ReturnCode);
  147.   end;
  148.  
  149.   procedure RemoveMarkFile;
  150.     {-Close and remove the mark file}
  151.   begin
  152.     Close(MarkF);
  153.     if IoResult = 0 then
  154.       if not KeepMark then begin
  155.         Erase(MarkF);
  156.         if IoResult = 0 then ;
  157.       end;
  158.     MarkFOpen := False;
  159.   end;
  160.  
  161.   procedure Abort(Msg : String);
  162.     {-Halt in case of error}
  163.   begin
  164.     if MarkFOpen then
  165.       RemoveMarkFile;
  166.     WriteLn(Msg);
  167.     Halt(255);
  168.   end;
  169.  
  170.   function FindMark(MarkName, MarkID : String;
  171.                     MarkOffset : Word;
  172.                     var MemMark, FilMark : Boolean;
  173.                     var B : BlockType) : Boolean;
  174.     {-Find the last memory block matching idstring at offset idoffset}
  175.   var
  176.     BPsp : Word;
  177.  
  178.     function HasIDstring(Segment : Word;
  179.                          IdString : String;
  180.                          IdOffset : Word) : Boolean;
  181.       {-Return true if idstring is found at segment:idoffset}
  182.     var
  183.       Tstring : String;
  184.       Len : Byte;
  185.     begin
  186.       Len := Length(IdString);
  187.       Tstring[0] := Chr(Len);
  188.       Move(Mem[Segment:IdOffset], Tstring[1], Len);
  189.       HasIDstring := (Tstring = IdString);
  190.     end;
  191.  
  192.     function GetMarkName(Segment : Word) : String;
  193.       {-Return a cleaned up mark name from the segment's PSP}
  194.     var
  195.       Tstring : String;
  196.       Tlen : Byte absolute Tstring;
  197.     begin
  198.       Move(Mem[Segment:$80], Tstring[0], 128);
  199.       while (Tlen > 0) and ((Tstring[1] = ' ') or (Tstring[1] = ^I)) do
  200.         Delete(Tstring, 1, 1);
  201.       while (Tlen > 0) and ((Tstring[Tlen] = ' ') or (Tstring[Tlen] = ^I)) do
  202.         Dec(Tlen);
  203.       GetMarkName := StUpcase(Tstring);
  204.     end;
  205.  
  206.     function MatchMemMark(Segment : Word;
  207.                           MarkName : String;
  208.                           var B : BlockType) : Boolean;
  209.       {-Return true if MemMark is unnamed or matches current name}
  210.     var
  211.       FoundIt : Boolean;
  212.       Tstring : String;
  213.     begin
  214.       {Check the mark name stored in the PSP of the mark block}
  215.       Tstring := GetMarkName(Segment);
  216.       FoundIt := (Tstring = MarkName);
  217.       if not FoundIt then begin
  218.         if (Tstring <> '') and (Tstring[1] = ProtectChar) then
  219.           {Current mark is protected, stop searching}
  220.           B := 1;
  221.         Dec(B);
  222.       end;
  223.       MatchMemMark := FoundIt;
  224.     end;
  225.  
  226.     function MatchFilMark(Segment : Word;
  227.                           MarkName : String;
  228.                           var B : BlockType) : Boolean;
  229.       {-Return true if FilMark is unnamed or matches current name}
  230.     var
  231.       FoundIt : Boolean;
  232.     begin
  233.       {Check the mark name stored in the PSP of the mark block}
  234.       FoundIt := (GetMarkName(Segment) = MarkName);
  235.       if FoundIt then begin
  236.         {Assure named file exists}
  237.         if Verbose then
  238.           WriteLn('Finding mark file ', MarkName);
  239.         FoundIt := ExistFile(MarkName);
  240.       end;
  241.       if not FoundIt then
  242.         {Net marks are protected marks; stop checking if non-match found}
  243.         B := 0;
  244.       MatchFilMark := FoundIt;
  245.     end;
  246.  
  247.     function MatchExactFilMark(Segment : Word;
  248.                                MarkName : String;
  249.                                var B : BlockType) : Boolean;
  250.       {-Return true if FilMark matches current name}
  251.     var
  252.       FoundIt : Boolean;
  253.     begin
  254.       {Check the mark name stored in the PSP of the mark block}
  255.       FoundIt := (GetMarkName(Segment) = MarkName);
  256.       if FoundIt then begin
  257.         {Assure named file exists}
  258.         if Verbose then
  259.           WriteLn('Finding mark file ', MarkName);
  260.         FoundIt := ExistFile(MarkName);
  261.       end;
  262.       if not FoundIt then
  263.         dec(B);
  264.       MatchExactFilMark := FoundIt;
  265.     end;
  266.  
  267.   begin
  268.     B := BlockMax;
  269.     MemMark := False;
  270.     FilMark := False;
  271.     if UseHiMem then begin
  272.       {Scan for an exact match to the specified net mark}
  273.       repeat
  274.         BPsp := Blocks[B].Psp;
  275.         if (Blocks[B].Mcb+1 <> BPsp) or (BPsp = PrefixSeg) then
  276.           {Don't match any non-program block or this program}
  277.           Dec(B)
  278.         else if HasIDstring(BPsp, NmarkID, NmarkOffset) then
  279.           {A net mark}
  280.           FilMark := MatchExactFilMark(BPsp, MarkName, B)
  281.         else
  282.           {Not a net mark}
  283.           Dec(B);
  284.       until (B < 1) or FilMark;
  285.  
  286.     end else begin
  287.       {Scan from the last block down to find the last MARK TSR}
  288.       repeat
  289.         BPsp := Blocks[B].Psp;
  290.         if (Blocks[B].Mcb+1 <> BPsp) or (BPsp = PrefixSeg) then
  291.           {Don't match any non-program block or this program}
  292.           Dec(B)
  293.         else if HasIDstring(BPsp, MarkID, MarkOffset) then
  294.           {An in-memory mark}
  295.           MemMark := MatchMemMark(BPsp, MarkName, B)
  296.         else if HasIDstring(BPsp, NmarkID, NmarkOffset) then
  297.           {A net mark}
  298.           FilMark := MatchFilMark(BPsp, MarkName, B)
  299.         else
  300.           {Ignore normal file marks}
  301.           {Not a mark}
  302.           Dec(B);
  303.       until (B < 1) or MemMark or FilMark;
  304.     end;
  305.     FindMark := MemMark or FilMark;
  306.   end;
  307.  
  308.   procedure CheckReadError;
  309.     {-Check previous I/O operation}
  310.   begin
  311.     if IoResult = 0 then
  312.       Exit;
  313.     Abort('Error reading '+MarkName);
  314.   end;
  315.  
  316.   function PhysicalAddress(P : Pointer) : LongInt;
  317.   begin
  318.     PhysicalAddress := LongInt(OS(P).S) shl 4+OS(P).O;
  319.   end;
  320.  
  321.   procedure ValidateMarkFile;
  322.     {-Open mark file and assure it's valid}
  323.   type
  324.     IDArray = array[1..4] of Char;
  325.   var
  326.     ID : IDArray;
  327.     ExpectedID : IDArray;
  328.   begin
  329.     Assign(MarkF, MarkName);
  330.     Reset(MarkF, 1);
  331.     if IoResult <> 0 then
  332.       Abort('Mark file '+MarkName+' not found');
  333.     MarkFOpen := True;
  334.  
  335.     {Check the ID at the start of the file}
  336.     ExpectedID := NetMarkID;
  337.     BlockRead(MarkF, ID, SizeOf(IDArray));
  338.     CheckReadError;
  339.     if ID <> ExpectedID then
  340.       Abort(MarkName+' is not a valid net mark file');
  341.  
  342.     {Read the NUL device address}
  343.     BlockRead(MarkF, TestPtr, SizeOf(Pointer));
  344.     CheckReadError;
  345.     if PhysicalAddress(TestPtr) <> PhysicalAddress(DevicePtr) then begin
  346.       if Verbose then
  347.         WriteLn('Old NUL addr:', HexPtr(TestPtr),
  348.                 '   Current NUL addr:', HexPtr(DevicePtr));
  349.       Abort('Unexpected error. NUL device moved');
  350.     end;
  351.   end;
  352.  
  353.   procedure BufferFileTable;
  354.     {-Read the file table from the mark file into memory}
  355.   type
  356.     SftRecStub =
  357.       record
  358.         Next : SftRecPtr;
  359.         Count : Word;
  360.       end;
  361.   var
  362.     I : Word;
  363.     Size : Word;
  364.     P : Pointer;
  365.     S : SftRecStub;
  366.   begin
  367.     BlockRead(MarkF, FileTableCnt, SizeOf(Word));
  368.     for I := 1 to FileTableCnt do begin
  369.       BlockRead(MarkF, S, SizeOf(SftRecStub));
  370.       Size := 6+S.Count*FileRecSize;
  371.       GetMem(FileTableA[I], Size);
  372.       P := FileTableA[I];
  373.       Move(S, P^, SizeOf(SftRecStub));
  374.       Inc(OS(P).O, SizeOf(SftRecStub));
  375.       BlockRead(MarkF, P^, Size-SizeOf(SftRecStub));
  376.     end;
  377.     CheckReadError;
  378.   end;
  379.  
  380.   procedure ReadReg(var B : Byte);
  381.     {-Read a communications register from the mark file}
  382.   begin
  383.     BlockRead(MarkF, B, SizeOf(Byte));
  384.     CheckReadError;
  385.   end;
  386.  
  387.   procedure ReadMarkFile;
  388.     {-Read the mark file info into memory}
  389.   var
  390.     DevPtr : DeviceHeaderPtr;
  391.     Com : Byte;
  392.   begin
  393.     {Read the vector table from the mark file, into a temporary memory area}
  394.     BlockRead(MarkF, Vectors, 1024);
  395.     CheckReadError;
  396.  
  397.     {Read the BIOS miscellaneous save areas into temporary tables}
  398.     BlockRead(MarkF, EGAsavTable, 8);
  399.     BlockRead(MarkF, IntComTable, 16);
  400.     BlockRead(MarkF, ParentSeg, 2);
  401.     BlockRead(MarkF, ParentLen, 2);
  402.     BlockRead(MarkF, BiosPrintTable, 10);
  403.     CheckReadError;
  404.  
  405.     {Read the stored EMS handles, if any}
  406.     BlockRead(MarkF, MarkEHandles, SizeOf(Word));
  407.     GetMem(MarkEmsHandles, SizeOf(HandlePageRecord)*MarkEHandles);
  408.     BlockRead(MarkF, MarkEmsHandles^, SizeOf(HandlePageRecord)*MarkEHandles);
  409.     CheckReadError;
  410.  
  411.     {Read the stored XMS info, if any}
  412.     BlockRead(MarkF, MarkXHandles, SizeOf(Word));
  413.     GetMem(MarkXmsHandles, SizeOf(XmsHandleRecord)*MarkXHandles);
  414.     BlockRead(MarkF, MarkXmsHandles^, SizeOf(XmsHandleRecord)*MarkXHandles);
  415.     BlockRead(MarkF, MarkHmaStatus, SizeOf(Byte));
  416.     CheckReadError;
  417.  
  418.     {Read the device driver chain}
  419.     DevPtr := DevicePtr;
  420.     DevCnt := 0;
  421.     while OS(DevPtr).O <> $FFFF do begin
  422.       Inc(DevCnt);
  423.       GetMem(DevA[DevCnt], SizeOf(DeviceHeader));
  424.       BlockRead(MarkF, DevA[DevCnt]^, SizeOf(DeviceHeader));
  425.       CheckReadError;
  426.       with DevA[DevCnt]^ do
  427.         DevPtr := Ptr(NextHeaderSegment, NextHeaderOffset);
  428.     end;
  429.  
  430.     {Read the DOS data area table}
  431.     BlockRead(MarkF, DosData, $200);
  432.     CheckReadError;
  433.  
  434.     {Read the DOS internal variables table}
  435.     BlockRead(MarkF, DosTableSize, SizeOf(Word));
  436.     if DosTableSize <> 0 then begin
  437.       GetMem(DosTable, DosTableSize);
  438.       BlockRead(MarkF, DosTable^, DosTableSize);
  439.     end;
  440.     CheckReadError;
  441.  
  442.     {Read the internal file table}
  443.     BufferFileTable;
  444.  
  445.     {Read in the copy of COMMAND.COM's PSP}
  446.     BlockRead(MarkF, CommandPsp, $100);
  447.     CheckReadError;
  448.  
  449.     {Read in the address used for COMMAND.COM patching by NetWare}
  450.     BlockRead(MarkF, PatchOfst, SizeOf(Word));
  451.     BlockRead(MarkF, PatchSegm, SizeOf(Word));
  452.     CheckReadError;
  453.  
  454.     {Read in the DOS master environment}
  455.     BlockRead(MarkF, EnvLen, SizeOf(Word));
  456.     GetMem(EnvPtr, EnvLen);
  457.     BlockRead(MarkF, EnvPtr^, EnvLen);
  458.     CheckReadError;
  459.  
  460.     {Read in the communications data area}
  461.     BlockRead(MarkF, PicMask, SizeOf(Byte));
  462.     CheckReadError;
  463.     for Com := 1 to 2 do
  464.       with ComData[Com] do begin
  465.         BlockRead(MarkF, Base, SizeOf(Word));
  466.         CheckReadError;
  467.         if Base <> 0 then begin
  468.           ReadReg(IERReg);
  469.           ReadReg(LCRReg);
  470.           ReadReg(MCRReg);
  471.           ReadReg(BRLReg);
  472.           ReadReg(BRHreg);
  473.         end;
  474.       end;
  475.  
  476.     {Read in the allocated Mcb chain}
  477.     BlockRead(MarkF, McbG.Count, SizeOf(Word));
  478.     BlockRead(MarkF, McbG.Mcbs, 2*SizeOf(Word)*McbG.Count);
  479.     CheckReadError;
  480.  
  481.     {Close and possibly erase mark file}
  482.     RemoveMarkFile;
  483.   end;
  484.  
  485.   procedure RestoreCommState;
  486.     {-Restore the communications chips to their previous state}
  487.   var
  488.     Com : Byte;
  489.   begin
  490.     for Com := 1 to 2 do
  491.       with ComData[Com] do
  492.         if Base <> 0 then begin
  493.           Port[Base+IER] := IERReg; {Interrupt enable register}
  494.           NullJump;
  495.           Port[Base+MCR] := MCRReg; {Modem control register}
  496.           NullJump;
  497.           Port[Base+LCR] := LCRReg or $80; {Enable baud rate divisor registers}
  498.           NullJump;
  499.           Port[Base+BRL] := BRLReg; {Baud rate low}
  500.           NullJump;
  501.           Port[Base+BRH] := BRHReg; {Baud rate high}
  502.           NullJump;
  503.           Port[Base+LCR] := LCRReg; {Line control register}
  504.           NullJump;
  505.         end;
  506.     {Restore the interrupt mask}
  507.     Port[$21] := PicMask;
  508.   end;
  509.  
  510.   procedure CopyVectors;
  511.     {-Put interrupt vectors back into table}
  512.  
  513.     procedure Reset8259;
  514.       {-Reset the 8259 interrupt controller to its powerup state}
  515.       {-Interrupts assumed OFF prior to calling this routine}
  516.  
  517.       function ATmachine : Boolean;
  518.         {-Return true if machine is AT class}
  519.       var
  520.         MachType : Byte absolute $FFFF : $000E;
  521.       begin
  522.         case MachType of
  523.           $F8, $FC : ATmachine := True;
  524.         else
  525.           ATmachine := False;
  526.         end;
  527.       end;
  528.  
  529.       procedure Reset8259PC;
  530.         {-Reset the 8259 on a PC class machine}
  531.       begin
  532.         inline(
  533.           $E4/$21/                { in      al,$21}
  534.           $88/$C4/                { mov     ah,al}
  535.           $B0/$13/                { mov     al,$13}
  536.           $E6/$20/                { out     $20,al}
  537.           $B0/$08/                { mov     al,8}
  538.           $E6/$21/                { out     $21,al}
  539.           $B0/$09/                { mov     al,9}
  540.           $E6/$21/                { out     $21,al}
  541.           $88/$E0/                { mov     al,ah}
  542.           $E6/$21                 { out     $21,al}
  543.           );
  544.       end;
  545.  
  546.       procedure Reset8259AT;
  547.         {-Reset the 8259 interrupt controllers on an AT machine}
  548.       begin
  549.         inline(
  550.           $32/$C0/                { xor       al,al }
  551.           $E6/$F1/                { out       0f1h,al         ; Switch off an 80287 if necessary}
  552.           {Set up master 8259 }
  553.           $E4/$21/                { in        al,21h          ; Get current interrupt mask }
  554.           $8A/$E0/                { mov       ah,al           ; save it }
  555.           $B0/$11/                { mov       al,11h }
  556.           $E6/$20/                { out       20h,al }
  557.           $EB/$00/                { jmp       short $+2 }
  558.           $B0/$08/                { mov       al,8            ; Set up main interrupt vector number}
  559.           $E6/$21/                { out       21h,al }
  560.           $EB/$00/                { jmp       short $+2 }
  561.           $B0/$04/                { mov       al,4 }
  562.           $E6/$21/                { out       21h,al }
  563.           $EB/$00/                { jmp       short $+2 }
  564.           $B0/$01/                { mov       al,1 }
  565.           $E6/$21/                { out       21h,al }
  566.           $EB/$00/                { jmp       short $+2 }
  567.           $8A/$C4/                { mov       al,ah }
  568.           $E6/$21/                { out       21h,al }
  569.           {Set up slave 8259 }
  570.           $E4/$A1/                { in        al,0a1h         ; Get current interrupt mask }
  571.           $8A/$E0/                { mov       ah,al           ; save it }
  572.           $B0/$11/                { mov       al,11h }
  573.           $E6/$A0/                { out       0a0h,al }
  574.           $EB/$00/                { jmp       short $+2 }
  575.           $B0/$70/                { mov       al,70h }
  576.           $E6/$A1/                { out       0a1h,al }
  577.           $B0/$02/                { mov       al,2 }
  578.           $EB/$00/                { jmp       short $+2 }
  579.           $E6/$A1/                { out       0a1h,al }
  580.           $EB/$00/                { jmp       short $+2 }
  581.           $B0/$01/                { mov       al,1 }
  582.           $E6/$A1/                { out       0a1h,al }
  583.           $EB/$00/                { jmp       short $+2 }
  584.           $8A/$C4/                { mov       al,ah           ; Reset previous interrupt state }
  585.           $E6/$A1                 { out       0a1h,al }
  586.           );
  587.       end;
  588.  
  589.     begin
  590.       if ATmachine then
  591.         Reset8259AT
  592.       else
  593.         Reset8259PC;
  594.     end;
  595.  
  596.   begin
  597.     {Interrupts off}
  598.     IntsOff;
  599.  
  600.     {Reset 8259 if requested}
  601.     if Revector8259 then
  602.       Reset8259;
  603.  
  604.     {Reset the communications state if requested}
  605.     if RestoreComm then
  606.       RestoreCommState;
  607.  
  608.     {Restore the main interrupt vector table}
  609.     Move(Vectors, Mem[0:0], 1024);
  610.  
  611.     {Interrupts on}
  612.     IntsOn;
  613.  
  614.     {Flag that we don't want system restoring vectors for us}
  615.     VectorsRestored := True;
  616.  
  617.     Move(EGAsavTable, Mem[$40:$A8], 8); {EGA table}
  618.     Move(IntComTable, Mem[$40:$F0], 16); {Interapplications communication area}
  619.     {$IFDEF Debug}
  620.     writeln('Parent address: ', HexW(ParentSeg), ' Length: ', ParentLen);
  621.     {$ENDIF}
  622.     if ValidPsp(HiMemSeg, ParentSeg, ParentLen) then begin
  623.       {Don't restore parent if it no longer exists (applies to QEMM LOADHI)}
  624.       MemW[PrefixSeg:$16] := ParentSeg;
  625.       if not UseHiMem then
  626.         {Programs loaded into high memory have strange termination addresses}
  627.         Move(Mem[0:4*$22], Mem[PrefixSeg:$0A], 4); {Int 22 addresses}
  628.     end;
  629.     Move(BiosPrintTable, Mem[$40:$08], 10); {BIOS Printer Table}
  630.     Move(Mem[0:4*$23], Mem[PrefixSeg:$0E], 8); {Int 23,24 addresses}
  631.   end;
  632.  
  633.   procedure MarkBlocks(markBlock : BlockType);
  634.     {-Mark those blocks to be released}
  635.  
  636.     procedure BatchWarning(B : BlockType);
  637.       {-Warn about the trapping effect of batch files}
  638.     var
  639.       T : BlockType;
  640.     begin
  641.       ReturnCode := 1;
  642.       {Accumulate number of bytes temporarily trapped}
  643.       for T := 1 to B do
  644.         if Blocks[T].ReleaseIt then
  645.           Inc(TrappedBytes, LongInt(MemW[Blocks[T].Mcb:3]) shl 4);
  646.     end;
  647.  
  648.     procedure MarkBlocksAbove;
  649.       {-Mark blocks above the mark}
  650.     var
  651.       b : BlockType;
  652.     begin
  653.       for b := 1 to BlockMax do
  654.         with Blocks[b] do
  655.           if (b >= markBlock) and (psp = CommandSeg) then begin
  656.             {Don't release blocks owned by master COMMAND.COM}
  657.             releaseIt := False;
  658.             BatchWarning(b);
  659.           end else if KeepMark then
  660.             {Release all but RELEASE and the mark}
  661.             releaseIt := (psp <> PrefixSeg) and (psp > markPsp)
  662.           else
  663.             releaseIt := (psp <> PrefixSeg) and (psp >= markPsp);
  664.     end;
  665.  
  666.     procedure MarkUnallocatedBlocks;
  667.       {-Mark blocks that weren't allocated at time of mark}
  668.     var
  669.       TopSeg : Word;
  670.       b : BlockType;
  671.       m : BlockType;
  672.       Found : Boolean;
  673.     begin
  674.       {Find last low memory mcb}
  675.       TopSeg := TopOfMemSeg-1;
  676.       m := 1;
  677.       Found := False;
  678.       while (not Found) and (m <= McbG.Count) do
  679.         if McbG.Mcbs[m].mcb >= TopSeg then
  680.           Found := True
  681.         else
  682.           inc(m);
  683.  
  684.       {Mark out all mcbs associated with psp of last low memory mcb}
  685.       TopSeg := McbG.Mcbs[m-1].psp;
  686.       if TopSeg <> markPsp then
  687.         for m := 1 to McbG.Count do
  688.           with McbG.Mcbs[m] do
  689.             if psp = TopSeg then
  690.               psp := 0;
  691.  
  692.       for b := 1 to BlockMax do
  693.         with Blocks[b] do begin
  694.           Found := False;
  695.           m := 1;
  696.           while (not Found) and (m <= McbG.Count) do begin
  697.             Found := (McbG.Mcbs[m].psp <> 0) and (McbG.Mcbs[m].mcb = mcb);
  698.             inc(m);
  699.           end;
  700.           if Found then
  701.             {was allocated at time of mark, keep it now unless a mark to be released}
  702.             releaseIt := not KeepMark and (psp = markPsp)
  703.           else if psp = CommandSeg then
  704.             {Don't release blocks owned by master COMMAND.COM}
  705.             releaseIt := False
  706.           else
  707.             {not allocated at time of mark}
  708.             releaseIt := (psp <> 0) and (psp <> PrefixSeg);
  709.         end;
  710.     end;
  711.  
  712.   begin
  713.     if UseHiMem then
  714.       MarkUnallocatedBlocks
  715.     else
  716.       MarkBlocksAbove;
  717.  
  718.     {$IFDEF Debug}
  719.     for b := 1 to BlockMax do
  720.       with Blocks[b] do
  721.         WriteLn(b:3, ' ', HexW(psp), ' ', HexW(mcb), ' ', releaseIt);
  722.     {$ENDIF}
  723.   end;
  724.  
  725.   function ReleaseBlock(Segm : Word) : Word; assembler;
  726.     {-Use DOS services to release memory block}
  727.   asm
  728.     mov ah,$49
  729.     mov es,Segm
  730.     int $21
  731.     jc  @Done
  732.     xor ax,ax
  733. @Done:
  734.   end;
  735.  
  736.   procedure ReleaseMem;
  737.     {-Release DOS memory marked for release}
  738.   var
  739.     b : BlockType;
  740.   begin
  741.     if Verbose then begin
  742.       WriteLn('Releasing DOS memory');
  743.       {$IFDEF Debug}
  744.       ReadLn;
  745.       {$ENDIF}
  746.     end;
  747.     for b := BlockMax downto 1 do
  748.       with blocks[b] do
  749.         if releaseIt then
  750.           if ReleaseBlock(mcb+1) <> 0 then begin
  751.             WriteLn('Could not release block at segment ', HexW(mcb+1));
  752.             Abort('Memory may be a mess... Please reboot');
  753.           end;
  754.     if Verbose then begin
  755.       WriteLn('Merging free blocks in high memory');
  756.       {$IFDEF Debug}
  757.       ReadLn;
  758.       {$ENDIF}
  759.     end;
  760.     MergeHiMemBlocks(HiMemSeg);
  761.   end;
  762.  
  763.   procedure RestoreEMSmap;
  764.     {-Restore EMS to state at time of mark}
  765.   var
  766.     O, N, NHandle : Word;
  767.  
  768.     procedure EmsError;
  769.     begin
  770.       WriteLn('Program error or EMS device not responding');
  771.       Abort('EMS memory may be a mess... Please reboot');
  772.     end;
  773.  
  774.   begin
  775.     {Get the existing EMS page map}
  776.     GetMem(CurrEmsHandles, MaxHandles*SizeOf(HandlePageRecord));
  777.     CurrEHandles := EmsHandles(CurrEmsHandles^);
  778.     if CurrEHandles > MaxHandles then
  779.       WriteLn('EMS handle count exceeds capacity of RELNET -- no action taken')
  780.     else if CurrEHandles <> 0 then begin
  781.       {See how many handles were active when MARK was installed}
  782.       if Verbose then begin
  783.         WriteLn('Releasing EMS memory allocated since MARK');
  784.         {$IFDEF Debug}
  785.         ReadLn;
  786.         {$ENDIF}
  787.       end;
  788.       {Compare the two maps and deallocate pages not in the stored map}
  789.       for N := 1 to CurrEHandles do begin
  790.         {Scan all current handles}
  791.         NHandle := CurrEmsHandles^[N].Handle;
  792.         if MarkEHandles > 0 then begin
  793.           {See if current handle matches one stored by MARK}
  794.           O := 1;
  795.           while (MarkEmsHandles^[O].Handle <> NHandle) and (O <= MarkEHandles) do
  796.             Inc(O);
  797.           {If not, deallocate the current handle}
  798.           if (O > MarkEHandles) then
  799.             if not FreeEms(NHandle) then
  800.               EmsError;
  801.         end else
  802.           {No handles stored by MARK, deallocate all current handles}
  803.           if not FreeEms(NHandle) then
  804.             EmsError;
  805.       end;
  806.     end;
  807.   end;
  808.  
  809.   procedure RestoreXmsmap;
  810.     {-Restore Xms to state at time of mark}
  811.   var
  812.     O, N, NHandle : Word;
  813.  
  814.     procedure XmsError;
  815.     begin
  816.       WriteLn('Program error or XMS device not responding');
  817.       Abort('XMS memory may be a mess... Please reboot');
  818.     end;
  819.  
  820.   begin
  821.     CurrXHandles := GetXmsHandles(CurrXmsHandles);
  822.     if CurrXHandles <> 0 then begin
  823.       {See how many handles were active when MARK was installed}
  824.       if Verbose then begin
  825.         WriteLn('Releasing XMS memory allocated since MARK');
  826.         {$IFDEF Debug}
  827.         ReadLn;
  828.         {$ENDIF}
  829.       end;
  830.       if MarkXHandles = 0 then begin
  831.         {Release all current XMS Handles}
  832.         for N := 1 to CurrXHandles do
  833.           if FreeExtMem(CurrXmsHandles^[N].Handle) <> 0 then
  834.             XmsError;
  835.       end else begin
  836.         {Compare the two maps and deallocate pages not in the stored map}
  837.         for N := 1 to CurrXHandles do begin
  838.           {Scan all current handles}
  839.           NHandle := CurrXmsHandles^[N].Handle;
  840.           {See if current handle matches one stored by MARK}
  841.           O := 1;
  842.           while (MarkXmsHandles^[O].Handle <> NHandle) and (O <= MarkXHandles) do
  843.             Inc(O);
  844.           {If not, deallocate the current handle}
  845.           if (O > MarkXHandles) then
  846.             if FreeExtMem(NHandle) <> 0 then
  847.               XmsError;
  848.         end;
  849.       end;
  850.     end;
  851.  
  852.     {Free the HMA if appropriate}
  853.     CurHmaStatus := AllocateHma($FFFF);
  854.     if (CurHMAStatus = 0) or (MarkHMAStatus = 0) then
  855.       if FreeHma = 0 then ;
  856.   end;
  857.  
  858.   procedure GetOptions;
  859.     {-Analyze command line for options}
  860.  
  861.     procedure WriteCopyright;
  862.     begin
  863.       WriteLn('RELNET ', Version, ', Copyright 1991 TurboPower Software');
  864.     end;
  865.  
  866.     procedure WriteHelp;
  867.       {-Show the options}
  868.     begin
  869.       WriteCopyright;
  870.       WriteLn;
  871.       WriteLn('RELNET removes memory-resident programs from memory, particularly network');
  872.       WriteLn('shells like Novell''s NetWare, although it will also release normal memory');
  873.       WriteLn('resident programs. In combination with MARKNET it thoroughly restores the');
  874.       WriteLn('system to its state at the time MARKNET was called.');
  875.       WriteLn;
  876.       WriteLn('RELNET accepts the following command line syntax:');
  877.       WriteLn;
  878.       WriteLn('  RELNET NetMarkFile [Options]');
  879.       WriteLn;
  880.       WriteLn('Options may be preceded by either / or -. Valid options are:');
  881.       WriteLn;
  882.       WriteLn('  /C         do NOT restore communications state.');
  883.       WriteLn('  /E         do NOT access EMS memory.');
  884.       WriteLn('  /H         work with upper memory if available.');
  885.       WriteLn('  /I         do NOT shut down IPX events and sockets.');
  886.       WriteLn('  /K         release memory, but keep the mark in place.');
  887.       WriteLn('  /P         do NOT restore DOS environment.');
  888.       WriteLn('  /Q         write no screen output.');
  889.       WriteLn('  /R         revector 8259 interrupt controller to powerup state.');
  890.       WriteLn('  /S chars   stuff string (<16 chars) into keyboard buffer on exit.');
  891.       WriteLn('  /T         do NOT reset system timer chip to default rate.');
  892.       WriteLn('  /U         work with upper memory, but halt if none found.');
  893.       WriteLn('  /V         verbose: show each step of the restore.');
  894.       WriteLn('  /X         do NOT access XMS memory.');
  895.       WriteLn('  /?         write this help screen.');
  896.       Halt(1);
  897.     end;
  898.  
  899.     procedure GetArgs(S : String);
  900.     var
  901.       SPos : Word;
  902.       Arg : String[127];
  903.     begin
  904.       SPos := 1;
  905.       repeat
  906.         Arg := NextArg(S, SPos);
  907.         if Arg = '' then
  908.           Exit;
  909.         if Arg[1] = '?' then
  910.           WriteHelp
  911.         else if (Arg[1] = '-') or (Arg[1] = '/') then
  912.           case Length(Arg) of
  913.             1 : Abort('Missing command option following '+Arg);
  914.             2 : case Upcase(Arg[2]) of
  915.                   'C' : RestoreComm := False;
  916.                   'E' : DealWithEMS := False;
  917.                   'H' : OptUseHiMem := True;
  918.                   'I' : DealWithIPX := False;
  919.                   'K' : KeepMark := True;
  920.                   'P' : RestoreEnvir := False;
  921.                   'Q' : Quiet := True;
  922.                   'R' : Revector8259 := True;
  923.                   'S' : begin
  924.                           Arg := NextArg(S, SPos);
  925.                           if Length(Arg) = 0 then
  926.                             Abort('Key string missing');
  927.                           if Length(Arg) > 15 then
  928.                             Abort('No more than 15 keys may be stuffed');
  929.                           Keys := Arg+^M;
  930.                         end;
  931.                   'T' : ResetTimer := False;
  932.                   'U' : UseHiMem := True;
  933.                   'V' : Verbose := True;
  934.                   'X' : DealWithXMS := False;
  935.                   '?' : WriteHelp;
  936.                 else
  937.                   Abort('Unknown command option: '+Arg);
  938.                 end;
  939.           else
  940.             Abort('Unknown command option: '+Arg);
  941.           end
  942.         else if Length(MarkName) = 0 then
  943.           {Mark file}
  944.           MarkName := StUpcase(Arg)
  945.         else
  946.           Abort('Too many mark files specified');
  947.       until False;
  948.     end;
  949.  
  950.   begin
  951.     {Initialize defaults}
  952.     MarkName := '';
  953.     Keys := '';
  954.  
  955.     Revector8259 := False;
  956.     KeepMark := False;
  957.     DealWithIPX := True;
  958.     DealWithEMS := True;
  959.     DealWithXMS := True;
  960.     ResetTimer := True;
  961.     Verbose := False;
  962.     Quiet := False;
  963.     RestoreEnvir := True;
  964.     RestoreComm := True;
  965.     UseHiMem := False;
  966.     OptUseHiMem := False;
  967.  
  968.     ReturnCode := 0;
  969.     TrappedBytes := 00;
  970.  
  971.     {Get arguments from the command line and the environment}
  972.     GetArgs(StringPtr(Ptr(PrefixSeg, $80))^);
  973.     GetArgs(GetEnv('RELNET'));
  974.  
  975.     if Length(MarkName) = 0 then begin
  976.       WriteLn('No mark file specified');
  977.       WriteHelp;
  978.     end;
  979.     if Verbose then
  980.       Quiet := False;
  981.     if not Quiet then
  982.       WriteCopyright;
  983.  
  984.     {Initialize for high memory access}
  985.     if OptUseHiMem or UseHiMem then begin
  986.       HiMemSeg := FindHiMemStart;
  987.       if HiMemSeg = 0 then begin
  988.         if UseHiMem then
  989.           Abort('No upper memory blocks found');
  990.       end else
  991.         UseHiMem := True;
  992.     end else
  993.       HiMemSeg := 0;
  994.   end;
  995.  
  996.   function MemoryRelease(P : Pointer) : Boolean;
  997.     {-Return True if address P is in a block to be released}
  998.   var
  999.     B : BlockType;
  1000.     PL : LongInt;
  1001.     PSPL : LongInt;
  1002.   begin
  1003.     PL := PhysicalAddress(P);
  1004.     for B := 1 to BlockMax do
  1005.       with Blocks[B] do
  1006.         if ReleaseIt then begin
  1007.           PSPL := LongInt(Psp) shl 4;
  1008.           if (PL >= PSPL) and (PL < PSPL+LongInt(MemW[Mcb:3]) shl 4) then begin
  1009.             MemoryRelease := True;
  1010.             Exit;
  1011.           end;
  1012.         end;
  1013.     MemoryRelease := False;
  1014.   end;
  1015.  
  1016.   procedure CloseIpxSockets;
  1017.   const
  1018.     Retf : Byte = $CB; {Return instruction}
  1019.   var
  1020.     This, Next : IpxEcbPtr;
  1021.     Ecb : IpxEcb;
  1022.     Status : Byte;
  1023.   begin
  1024.     {Create a new Ecb to find start of linked list of Ecb's}
  1025.     FillChar(Ecb, SizeOf(IpxEcb), 0);
  1026.     Ecb.EsrAddress := @RetF;
  1027.     ScheduleSpecialEvent(182, Ecb);
  1028.  
  1029.     {Scan the list of Ecb's}
  1030.     This := Ecb.Link;
  1031.     while This <> nil do begin
  1032.       if Verbose then
  1033.         Write('Ecb: ', HexPtr(This),
  1034.               ' Esr: ', HexPtr(This^.EsrAddress),
  1035.               ' InUse: ', HexW(This^.InUse),
  1036.               ' Socket: ', HexW(This^.SocketNumber));
  1037.       Next := This^.Link;
  1038.       if MemoryRelease(This) or MemoryRelease(This^.ESRAddress) then
  1039.         {Memory of this Ecb will be released}
  1040.         if This^.InUse <> 0 then begin
  1041.           {This Ecb is in use}
  1042.           Status := CancelEvent(This^);
  1043.           if Verbose then
  1044.             Write(' [cancelled]');
  1045.           if This^.SocketNumber <> 0 then begin
  1046.             CloseSocket(This^.SocketNumber);
  1047.             if Verbose then
  1048.               Write(' [closed]');
  1049.           end;
  1050.         end;
  1051.       if Verbose then
  1052.         Writeln;
  1053.       This := Next;
  1054.     end;
  1055.  
  1056.     {Cancel the special event we started}
  1057.     Status := CancelEvent(Ecb);
  1058.   end;
  1059.  
  1060.   procedure FindDevChain;
  1061.     {-Return segment, offset and pointer to NUL device}
  1062.   begin
  1063.     DosPtr := Ptr(OS(DosList).S, OS(DosList).O-2);
  1064.     DevicePtr := @DosPtr^.NullDevice;
  1065.     DeviceSegment := OS(DevicePtr).S;
  1066.     DeviceOffset := OS(DevicePtr).O;
  1067.   end;
  1068.  
  1069.   procedure RestoreDosTable;
  1070.     {-Restore the DOS variables table, except for the buffer pointer}
  1071.   type
  1072.     ByteArray = array[0..32767] of Byte;
  1073.     ByteArrayPtr = ^ByteArray;
  1074.   var
  1075.     DosBase : Pointer;
  1076.     SPtr : Pointer;
  1077.     DPtr : Pointer;
  1078.   begin
  1079.     if Verbose then begin
  1080.       WriteLn('Restoring DOS data area at 0050:0000');
  1081.       {$IFDEF Debug}
  1082.       ReadLn;
  1083.       {$ENDIF}
  1084.     end;
  1085.     DPtr := Ptr($50, 0);
  1086.     Move(DosData, DPtr^, $200);
  1087.  
  1088.     DosBase := Ptr(OS(DosPtr).S, 0);
  1089.     if Verbose then begin
  1090.       WriteLn('Restoring ', DosTableSize,
  1091.               ' bytes of DOS variables table at ', HexPtr(DosBase));
  1092.       {$IFDEF Debug}
  1093.       ReadLn;
  1094.       {$ENDIF}
  1095.     end;
  1096.  
  1097.     {patch up DosTable to reflect current items that must be maintained}
  1098.     {CachePtr}
  1099.     SPtr := @DosPtr^.CachePtr;
  1100.     DPtr := @ByteArrayPtr(DosTable)^[Ofs(DosPtr^.CachePtr)];
  1101.     {$IFDEF Debug}
  1102.     writeln('cacheptr ', hexptr(sptr), '->', hexptr(dptr), ' ', SizeOf(Pointer));
  1103.     {$ENDIF}
  1104.  
  1105.     move(SPtr^, DPtr^, SizeOf(Pointer));
  1106.     if DosV = 5 then begin
  1107.       {Other unknown areas}
  1108.       SPtr := Ptr(OS(DosPtr).S, OS(DosPtr).O+SizeOf(DosRec));
  1109.       DPtr := @ByteArrayPtr(DosTable)^[OS(DosPtr).O+SizeOf(DosRec)];
  1110.       {$IFDEF Debug}
  1111.       writeln('unknown  ', hexptr(sptr), '->', hexptr(dptr), ' ',
  1112.               OS(DosPtr^.FirstSFT).O-OS(DosPtr).O-SizeOf(DosRec)-$3C);
  1113.       {$ENDIF}
  1114.       move(SPtr^, DPtr^, OS(DosPtr^.FirstSFT).O-OS(DosPtr).O-SizeOf(DosRec)-$3C);
  1115.     end;
  1116.  
  1117.     {Restore DOS table}
  1118.     move(DosTable^, DosBase^, DosTableSize);
  1119.   end;
  1120.  
  1121.   procedure RestoreFileTable;
  1122.     {-Copy the internal file table from our memory buffer to its DOS location}
  1123.   var
  1124.     S : SftRecPtr;
  1125.     I : Word;
  1126.   begin
  1127.     S := DosPtr^.FirstSFT;
  1128.     if Verbose then begin
  1129.       WriteLn('Restoring DOS file table at ', HexPtr(S));
  1130.       {$IFDEF Debug}
  1131.       ReadLn;
  1132.       {$ENDIF}
  1133.     end;
  1134.     for I := 1 to FileTableCnt do begin
  1135.       Move(FileTableA[I]^, S^, 6+FileTableA[I]^.Count*FileRecSize);
  1136.       S := S^.Next;
  1137.     end;
  1138.   end;
  1139.  
  1140.   procedure RestoreDeviceDrivers;
  1141.     {-Restore the device driver chain to its original state}
  1142.   var
  1143.     D : Word;
  1144.     DevPtr : DeviceHeaderPtr;
  1145.   begin
  1146.     if Verbose then begin
  1147.       WriteLn('Restoring device driver chain');
  1148.       {$IFDEF Debug}
  1149.       ReadLn;
  1150.       {$ENDIF}
  1151.     end;
  1152.     DevPtr := DevicePtr;
  1153.     for D := 1 to DevCnt do begin
  1154.       DevPtr^ := DevA[D]^;
  1155.       with DevA[D]^ do
  1156.         DevPtr := Ptr(NextHeaderSegment, NextHeaderOffset);
  1157.     end;
  1158.   end;
  1159.  
  1160.   procedure RestoreCommandPSP;
  1161.     {-Copy COMMAND.COM's PSP back into place}
  1162.   var
  1163.     PspPtr : Pointer;
  1164.   begin
  1165.     PspPtr := Ptr(CommandSeg, 0);
  1166.     if Verbose then begin
  1167.       WriteLn('Restoring COMMAND.COM PSP at ', HexPtr(PspPtr));
  1168.       {$IFDEF Debug}
  1169.       ReadLn;
  1170.       {$ENDIF}
  1171.     end;
  1172.     Move(CommandPsp, PspPtr^, $100);
  1173.   end;
  1174.  
  1175.   procedure RestoreCommandPatch;
  1176.     {-Restore the patch that NetWare applies to COMMAND.COM}
  1177.   begin
  1178.     if (PatchSegm <> 0) or (PatchOfst <> 0) then
  1179.       if (Mem[PatchSegm:PatchOfst+$01] <> Byte('/')) or
  1180.       (Mem[PatchSegm:PatchOfst+$11] <> Byte('/')) then begin
  1181.         if Verbose then begin
  1182.           WriteLn('Removing patch at ', HexW(PatchSegm), ':', HexW(PatchOfst));
  1183.           {$IFDEF Debug}
  1184.           ReadLn;
  1185.           {$ENDIF}
  1186.         end;
  1187.         Mem[PatchSegm:PatchOfst+$01] := Byte('/');
  1188.         Mem[PatchSegm:PatchOfst+$11] := Byte('/');
  1189.       end;
  1190.   end;
  1191.  
  1192.   procedure FindEnv(CommandSeg : Word; var EnvSeg, EnvLen : Word);
  1193.     {-Return the segment and length of the master environment}
  1194.   var
  1195.     Mcb : Word;
  1196.   begin
  1197.     Mcb := CommandSeg-1;
  1198.     EnvSeg := MemW[CommandSeg:$2C];
  1199.     if EnvSeg = 0 then
  1200.       {Master environment is next block past COMMAND}
  1201.       EnvSeg := Commandseg+MemW[Mcb:3]+1;
  1202.     EnvLen := MemW[(EnvSeg-1):3] shl 4;
  1203.   end;
  1204.  
  1205.   procedure RestoreDosEnvironment;
  1206.     {-Restore the master copy of the DOS environment}
  1207.   var
  1208.     EnvSeg : Word;
  1209.     CurLen : Word;
  1210.     P : Pointer;
  1211.   begin
  1212.     if RestoreEnvir then begin
  1213.       FindEnv(CommandSeg, EnvSeg, CurLen);
  1214.       if CurLen <> EnvLen then
  1215.         Abort('Environment length changed');
  1216.       if Verbose then begin
  1217.         WriteLn('Restoring DOS environment, ', EnvLen, ' bytes at ', HexW(EnvSeg), ':0000');
  1218.         {$IFDEF Debug}
  1219.         ReadLn;
  1220.         {$ENDIF}
  1221.       end;
  1222.       P := Ptr(EnvSeg, 0);
  1223.       move(EnvPtr^, P^, EnvLen);
  1224.     end;
  1225.   end;
  1226.  
  1227.   procedure SetTimerRate(Rate : Word);
  1228.     {-Program system 8253 timer number 0 to run at specified rate}
  1229.   begin
  1230.     IntsOff;
  1231.     Port[$43] := $36;
  1232.     NullJump;
  1233.     Port[$40] := Lo(Rate);
  1234.     NullJump;
  1235.     Port[$40] := Hi(Rate);
  1236.     IntsOn;
  1237.   end;
  1238.  
  1239.   procedure RestoreTimer;
  1240.     {-Set the system timer to its normal rate}
  1241.   begin
  1242.     if Verbose then begin
  1243.       WriteLn('Restoring system timer to normal rate');
  1244.       {$IFDEF Debug}
  1245.       ReadLn;
  1246.       {$ENDIF}
  1247.     end;
  1248.     SetTimerRate(0);
  1249.   end;
  1250.  
  1251.   function CompaqDOS30 : Boolean; assembler;
  1252.     {-Return true if Compaq DOS 3.0}
  1253.   asm
  1254.     mov ah,$34
  1255.     int $21
  1256.     cmp bx,$019C
  1257.     mov al,1
  1258.     jz @Done
  1259.     dec al
  1260. @Done:
  1261.   end;
  1262.  
  1263.   procedure ValidateDosVersion;
  1264.     {-Assure supported version of DOS and compute size of DOS internal filerec}
  1265.   var
  1266.     DosVer : Word;
  1267.   begin
  1268.     DosVer := DosVersion;
  1269.     case Hi(DosVer) of
  1270.       3 : if (Hi(DosVer) < $0A) and not CompaqDOS30 then
  1271.             {IBM DOS 3.0}
  1272.             FileRecSize := 56
  1273.           else
  1274.             {DOS 3.1+ or Compaq DOS 3.0}
  1275.             FileRecSize := 53;
  1276.       4, 5 : FileRecSize := 59;
  1277.     else
  1278.       Abort('Requires DOS 3, 4, or 5');
  1279.     end;
  1280.   end;
  1281.  
  1282. begin
  1283.   {Assure supported version of DOS}
  1284.   ValidateDosVersion;
  1285.  
  1286.   {Analyze command line for options}
  1287.   GetOptions;
  1288.  
  1289.   {Find the start of the device driver chain via the NUL device}
  1290.   FindDevChain;
  1291.  
  1292.   {Get all allocated memory blocks in normal memory}
  1293.   FindTheBlocks(True, HiMemSeg, Blocks, BlockMax, StartMcb, CommandSeg);
  1294.  
  1295.   {Find the block marked with the MARK idstring, and MarkName if specified}
  1296.   if not(FindMark(MarkName, MarkID, MarkOffset, MemMark, FilMark, markBlock)) then
  1297.     Abort('No matching marker found, or protected marker encountered.');
  1298.   if MemMark then
  1299.     Abort('Marker must have been placed by MARKNET');
  1300.   markPsp := Blocks[markBlock].psp;
  1301.  
  1302.   {Open and validate the mark file}
  1303.   ValidateMarkFile;
  1304.  
  1305.   {Close IPX sockets and cancel IPX ECBs}
  1306.   if DealWithIpx then
  1307.     if IpxInstalled then
  1308.       CloseIpxSockets;
  1309.  
  1310.   {Get file mark information into memory}
  1311.   ReadMarkFile;
  1312.  
  1313.   {Mark those blocks to be released}
  1314.   MarkBlocks(markBlock);
  1315.  
  1316.   {Copy the vector table from the MARK copy}
  1317.   CopyVectors;
  1318.  
  1319.   {Restore the device driver chain}
  1320.   RestoreDeviceDrivers;
  1321.  
  1322.   {Restore the COMMAND.COM patch possibly made by NetWare}
  1323.   RestoreCommandPatch;
  1324.  
  1325.   {Restore the DOS variables table}
  1326.   RestoreDosTable;
  1327.  
  1328.   {Restore the DOS file table}
  1329.   RestoreFileTable;
  1330.  
  1331.   {Restore the COMMAND.COM PSP}
  1332.   RestoreCommandPSP;
  1333.  
  1334.   {Restore the master DOS environment}
  1335.   RestoreDosEnvironment;
  1336.  
  1337.   {Set the timer to normal rate}
  1338.   if ResetTimer then
  1339.     RestoreTimer;
  1340.  
  1341. (*
  1342.   this isn't necessary, and in fact is harmful, when the DOS file table
  1343.   is being restored above.
  1344.   {Close open file handles}
  1345.   CloseHandles;
  1346. *)
  1347.  
  1348.   {Release normal memory}
  1349.   ReleaseMem;
  1350.  
  1351.   {Deal with expanded memory}
  1352.   if DealWithEMS then
  1353.     if EMSpresent then
  1354.       RestoreEMSmap;
  1355.  
  1356.   {Deal with extended memory}
  1357.   if DealWithXMS then
  1358.     if XMSInstalled then
  1359.       RestoreXMSMap;
  1360.  
  1361.   {Write success message}
  1362.   if not Quiet then
  1363.     WriteLn('Memory released after ', StUpcase(MarkName));
  1364.  
  1365.   if (ReturnCode <> 0) and Verbose then
  1366.     WriteLn(TrappedBytes, ' bytes temporarily trapped until batch file completes');
  1367.  
  1368.   {Stuff keyboard buffer if requested}
  1369.   if Length(Keys) > 0 then
  1370.     StuffKeys(Keys, True);
  1371.  
  1372.   NoRestoreHalt(ReturnCode);
  1373. end.
  1374.